
import { _decorator, Component, Node, Camera, director, PipelineEventType, Vec3, mat4, Mat4, v3, Material, MeshRenderer, Sprite, SpriteFrame, RenderTexture, gfx, Enum, Texture2D, view, screen, Canvas, find, Mesh } from 'cc';
import { GlobalUniformMgr } from './GlobalUniformMgr';
const { ccclass, property } = _decorator;

/**
 * Predefined variables
 * Name = WaterPlaneControl
 * DateTime = Fri Jan 28 2022 16:22:17 GMT+0800 (China Standard Time)
 * Author = 麒麟子
 * FileBasename = WaterPlaneControl.ts
 * FileBasenameNoExtension = WaterPlaneControl
 * URL = db://assets/resources/kylins_water_surface/WaterPlaneControl.ts
 * ManualUrl = https://docs.cocos.com/creator/3.4/manual/zh/
 *
 */

@ccclass('Mirror')
export class Mirror extends Component {

    @property({ type: Camera })
    mainCamera: Camera = null;

    @property({ type: Camera })
    refelctionCamera: Camera = null;

    @property({ type: MeshRenderer })
    mesh: MeshRenderer = null;

    private _lastCamera: Camera = null;

    start() {
        let texture = new RenderTexture();
        let size = screen.windowSize;

        texture.reset({
            width: size.width, height: size.height,
            passInfo: new gfx.RenderPassInfo(
                [new gfx.ColorAttachment(gfx.Format.RGBA8)],
                new gfx.DepthStencilAttachment(gfx.Format.DEPTH_STENCIL),
            )
        });
        this.refelctionCamera.targetTexture = texture;
        this.mesh.material.setProperty('albedoMap', texture);

        let globalUniformMgr = GlobalUniformMgr.inst;

        /*
        clipPlane.x = cc_fogBase.w;
        clipPlane.y = cc_nearFar.z;
        clipPlane.z = cc_nearFar.w;
        clipPlane.w = cc_time.w;

        isMirror cc_fogAdd.w;
        */

        director.root.pipeline.on(PipelineEventType.RENDER_CAMERA_BEGIN, (camera: Camera) => {
            if (camera.node['_id'] == this.refelctionCamera.node['_id']) {
                let n = this.mesh.node.forward;
                n = n.normalize();
                let d = -Vec3.dot(this.mesh.node.worldPosition, n);

                globalUniformMgr.setGlobalUniform('cc_fogBase.w', -n.x);
                globalUniformMgr.setGlobalUniform('cc_nearFar.z', -n.y);
                globalUniformMgr.setGlobalUniform('cc_nearFar.w', -n.z);
                globalUniformMgr.setGlobalUniform('cc_time.w', -d);

                globalUniformMgr.setGlobalUniform('cc_fogAdd.w', 1.0);

                this.mesh.enabled = false;

                director.getScene().emit('reflection_mode');
            }
            else if (camera.node['_id'] == this.mainCamera.node['_id']) {
                director.getScene().emit('scene_mode');
                globalUniformMgr.resetAll();
            }
        });

        director.root.pipeline.on(PipelineEventType.RENDER_CAMERA_END, (camera: Camera) => {
            if (camera.node['_id'] == this.refelctionCamera.node['_id']) {
                this.mesh.enabled = true;
            }
        });
    }

    lateUpdate() {
        if (!this.mainCamera) {
            return;
        }

        if (this.mesh && this.refelctionCamera && this.refelctionCamera.node.active) {
            let n = this.mesh.node.forward;
            n = n.normalize();
            let d = -Vec3.dot(this.mesh.node.worldPosition, n);
            this.mirrorObject(this.refelctionCamera.node, this.mainCamera.node, n, d);
        }
    }

    syncCameraParameters(current: Camera, target: Camera) {

    }

    private matTemp: Mat4 = mat4();
    mirrorObject(current: Node, target: Node, n: Vec3, d: number) {
        /*
            P = (x,y,z,1)
            Pm =(Xm,Ym,Zm,1)

            (Pm + P)/2  = P - N*D
                        = P - N*P·Plane
            => Pm   = P - 2 * （ N * (P · Plane) ）
                    = (x,y,z,w) - 2 * ((nx,ny,nz,0)*(x,y,z,w)·(nx,ny,nz,d))
                    = (x,y,z,w) - 2 (x*nx + y*ny + z * nz + w * d) * (nx,ny,nz,0)
            
            => Xm   = x - 2 (x*nx + y * ny + z * nz + w * d) * nx = x - (x,y,z,w) · (2*nx*nx,2*nx*ny,2*nx*nz,2*nx*d);
            => Ym   = y - 2 (x*nx + y * ny + z * nz + w * d) * ny = y - (x,y,z,w) · (2*ny*nx,2*ny*ny,2*nz*ny,2*ny*d);
            => Zm   = z - 2 (x*nx + y * ny + z * nz + w * d) * nz = z - (x,y,z,w) · (2*nz*nx,2*nz*ny,2*nz*nz,2*nz*d);
            => Wm   = w - 2 (x*nx + y * ny + z * nz + w * d) * 0 = w;
            | 1-2*nx*nx     -2*nx*ny        -2*nx*nz    0  |
            | -2*ny*nx      1 - 2*ny*ny     -2*ny*nz    0  |
            | -2*nz*nx      -2*nz*ny        1-2*nz*nz   0  |
            | -2*d*nx       -2*d*ny         -2*d*nz     1  |
        */

        let nx = n.x;
        let ny = n.y;
        let nz = n.z;
        let matReflection = mat4();
        matReflection.m00 = 1 - 2 * nx * nx;
        matReflection.m01 = -2 * ny * nx;
        matReflection.m02 = -2 * ny * nx;
        matReflection.m03 = 0;

        matReflection.m04 = -2 * nx * ny;
        matReflection.m05 = 1 - 2 * ny * ny;
        matReflection.m06 = -2 * nz * ny;
        matReflection.m07 = 0;

        matReflection.m08 = -2 * nx * nz;
        matReflection.m09 = -2 * ny * nz;
        matReflection.m10 = 1 - 2 * nz * nz;
        matReflection.m11 = 0;

        matReflection.m12 = - 2 * d * nx;
        matReflection.m13 = -2 * d * ny;
        matReflection.m14 = -2 * d * nz;
        matReflection.m15 = 1.0;

        target.getWorldMatrix(this.matTemp);
        Mat4.multiply(this.matTemp, matReflection, this.matTemp);

        current.matrix = this.matTemp;
        let worldScale = current.worldScale;
        current.setWorldScale(worldScale);
        current.setWorldScale(worldScale);
    }
}
